﻿using System;
using System.Collections.Generic;
using System.Linq;
using Elderos.Ontology;
using Elderos.Utils;

namespace Elderos.Highlight.Search
{
    public class RabinKarpSearcher : TextSearcherBase
    {
        private readonly int _prefixLength;
        private readonly StringComparer _stringComparer;

        private readonly IDictionary<string, List<int>> _keywords;

        private readonly IDictionary<string, Synonym> _synonyms;

        public RabinKarpSearcher(ISynonymSource synonymSource, int prefixLength = 10, StringComparer dictComparer = null)
            :base (synonymSource)
        {
            _prefixLength = prefixLength;
            _stringComparer = dictComparer ?? StringComparer.OrdinalIgnoreCase;
            _synonyms = new Dictionary<string, Synonym>(_stringComparer);

            _keywords = new Dictionary<string, List<int>>(_stringComparer);
        }

        protected override void ProcessSynonym(Synonym synonym)
        {
            string syno = synonym.Syno;

            if (_synonyms.ContainsKey(syno))
            {
                var firstSyno = _synonyms[syno];
                Synonym mainSyno = GetMainSyno(firstSyno, synonym);

                mainSyno.EntityIDs = synonym.EntityIDs.Union(firstSyno.EntityIDs).Distinct().ToList();

                _synonyms[syno] = mainSyno;
                return;
            }

            _synonyms.Add(syno, synonym);

            if (synonym.Syno.Length < _prefixLength) return;

            string hashKey = synonym.Syno.Substring(0, _prefixLength);

            List<int> lengths;
            if (!_keywords.TryGetValue(hashKey, out lengths))
                _keywords.Add(hashKey, lengths = new List<int>());

            if (!lengths.Contains(syno.Length))
                lengths.Add(syno.Length);
        }

        protected override List<Position> InnerSearch(PreparedText preparedText)
        {
            string text = preparedText.Text;

            var result = new List<Position>();
            for (int start = 0; start < text.Length; start++)
            {
                if (!text.IsCorrectLeftPosition(start)) continue;
                for (int length = 1; length < _prefixLength && start + length <= text.Length; length++)
                {
                    if (!text.IsCorrectRightPosition(start + length))
                        continue;

                    string pretender = text.Substring(start, length);
                    Synonym synonym;
                    if (_synonyms.TryGetValue(pretender, out synonym))
                    {
                        if (AsymmetricEquals(synonym.Syno, pretender))
                            result.Add(new Position(start, length, synonym.EntityIDs, pretender));
                    }
                }

                if (start + _prefixLength > text.Length) continue;

                string prefix = text.Substring(start, _prefixLength);
                List<int> lengths;
                if (!_keywords.TryGetValue(prefix, out lengths)) continue;

                foreach (var length in lengths)
                {
                    if (start + length > text.Length) continue;

                    if (!text.IsCorrectRightPosition(start + length))
                        continue;

                    string pretender = text.Substring(start, length);
                    Synonym synonym;

                    if (!_synonyms.TryGetValue(pretender, out synonym))
                        continue;

                    if (AsymmetricEquals(synonym.Syno, pretender))
                        result.Add(new Position(start, length, synonym.EntityIDs, synonym.Syno));
                }
            }

            return result;
        }
    }
}
